/***************************************************************************************
 * Copyright 2020 Infineon Technologies AG ( www.infineon.com ).                       *
 * All rights reserved.                                                                *
 *                                                                                     *
 * Licensed  Material-Property of Infineon Technologies AG.                            *
 * This software is made available solely pursuant to the terms of Infineon            *
 * Technologies AG agreement which governs its use. This code and the information      *
 * contained in it are proprietary and confidential to Infineon Technologies AG.       *
 * No person is allowed to copy, reprint, reproduce or publish any part of this code,  *
 * nor disclose its contents to others, nor make any use of it, nor allow or assist    *
 * others to make any use of it - unless by prior Written express authorization of     *
 * Infineon Technologies AG and then only to the extent authorized.                    *
 *                                                                                     *
 * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,            *
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY,           *
 * FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED.  IN NO       *
 * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,     *
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,                 *
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;         *
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY             *
 * WHETHER IN  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR            *
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF              *
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                          *
 *                                                                                     *
 ***************************************************************************************/
/**
 * @file   auths_api.c
 * @date   June, 2020
 * @brief  Implementation of Authenticate S SDK API
 */
#include <stdbool.h>
#include <stdlib.h>

#include "auths_api.h"
#include "register.h"
#include "helper.h"
#include "auths_config.h"
#include "auths_status.h"
#include "crypto_lib.h"


#include "i2c_bus.h"
#include "i2c_bus_command.h"




AuthS_Capability auths_capability;
AuthS_Capability *auths_capability_ptr;
AuthS_Enumeration auths_enumeration;

#define	DEFAULT_ECC_KEY_COUNT		(2)  //Every Authenticate S has 2 ECC key pairs
#define UID_SEARCH_RETRY            (3)  //Number of UID search retries

static uint16_t update_active_device_capabilities(void);

static void setup_device_attribute(AuthS_Capability *sdk_config){

	auths_capability.ecc_done = ecc_not_done;
	auths_capability.host_auth_done= host_auth_not_done;

	auths_capability.uid_length = UID_BYTE_LEN;
	//Setup the NVM size	
	auths_capability.nvm_size = CONFIG_NVM_SIZE;

	//Setup the NVM page size
	auths_capability.nvm_page_count = CONFIG_NVM_PAGE_COUNT;
	auths_capability.num_ecc_keypair = DEFAULT_ECC_KEY_COUNT;

}

/**
* @brief Return SDK version and release date.
* @param version SDK version value
* @param date Date release date.
*/
uint16_t auths_get_sdk_version(uint16_t* version, uint32_t* date) {
	uint16_t sdk_version = SDK_VERSION;
	uint32_t sdk_date = SDK_DATE;

	if (version == NULL) {
		return SDK_VERSION_NULL;
	}
	memcpy(version, &sdk_version, sizeof(sdk_version));

	if (date == NULL) {
		return SDK_DATE_NULL;
	}
	memcpy(date, &sdk_date, sizeof(sdk_date));

	return SDK_SUCCESS;
}

/**
* @brief Initialize the SDK
* @param selected_interface Interface
* @param sdk_config SDK configuration input
*/
uint16_t auths_init_sdk(AuthS_Interface selected_interface, AuthS_Capability* sdk_config) {

	uint16_t ret = SDK_INIT;

	if(sdk_config==NULL){
		return SDK_E_INPUT;
	}

	setup_device_attribute(sdk_config);	
//todo System porting required: implements GPIO, if required.
	auths_capability.gpo = NULL;

	switch(selected_interface.active_interface)
	{
	
	case SWI:{

		break;
    }

	
	case I2C:{

		auths_capability.active_interface = selected_interface.active_interface;
		auths_capability.interface.i2c.frequency = selected_interface.interface.i2c.frequency;

		//Assume there is a device on the bus with default address
		auths_capability.interface.i2c.device_count=1;
		auths_capability.interface.i2c.device_address=AUTHS_DEFAULT_ADDRESS;

		//Power cycle the default or first device address
		ret = auths_exe_power_cycle();
		if(ret!=EXE_SUCCESS){
			return ret;
		}
		break;
    }
	
	case UNDEFINED_INTERFACE:
		return SDK_E_INTERFACE_UNDEFINED;		

	default:
		return SDK_E_INTERFACE_UNDEFINED;		
	}
	
	//Initialize the hardware RNG, SHA and etc
	if(sdk_config->auths_alt_rng != NULL){
		auths_capability.auths_alt_rng = sdk_config->auths_alt_rng;
	}

	if(sdk_config->auths_alt_sha != NULL){
		auths_capability.auths_alt_sha = sdk_config->auths_alt_sha;
	}
	
	return EXE_SUCCESS;
}

/**
* @brief Return the number of device found on the bus
* @param device_found number of device found
*/
uint16_t auths_get_sdk_device_found(uint8_t * device_found)
{
	if(device_found== NULL){
		return SDK_E_INPUT;
	}
	*device_found = auths_enumeration.device_found;
	return SDK_SUCCESS;
}

/**
* @brief Return the current active device
* @param active_device active device
*/
uint16_t auths_get_sdk_active_device(uint8_t * active_device)
{
	if(active_device== NULL){
		return SDK_E_INPUT;
	}
	*active_device = auths_enumeration.active_device;
	return SDK_SUCCESS;
}

/**
* @brief Set and select the current active device.
* @param active_device active device
*/
uint16_t auths_set_sdk_active_device(uint8_t active_device)
{
	uint16_t ret;
	if(active_device > auths_enumeration.device_found){
		return SDK_E_INPUT;
	}

	auths_enumeration.active_device = active_device;
	
	if (auths_capability.active_interface ==I2C){
		auths_capability.interface.i2c.device_address = auths_enumeration.device_address[active_device];
	}

	//populate the SDK with the active device
	ret = update_active_device_capabilities();
	if(ret != SDK_SUCCESS){
		return ret;
	}

	return SDK_SUCCESS;
}

/**
* @brief Set and select the current active device by UID.
* @param active_device_uid active device uid
* @param invert the UID is stored in reverse and need to be flipped
*/
uint16_t auths_set_sdk_active_device_uid(uint8_t * active_device_uid, uint8_t invert)
{
	uint16_t ret;
	uint8_t compare_uid[12];
	uint8_t device_found = auths_enumeration.device_found;
	uint8_t mismatch=1;
	uint8_t device_found_index=0;

	
	if(invert==1){//flip the UID
		for(uint8_t j=0; j<12; j++){
			compare_uid[11-j] = active_device_uid[j];
		}
	}else{
		for(uint8_t j=0; j<12; j++){
			compare_uid[j] = active_device_uid[j];
		}
	}

	while(device_found){
		mismatch=0;
		for(uint8_t i=0; i<12; i++){
			if(compare_uid[i] != auths_enumeration.UID[device_found-1][i]){
				mismatch=1;
				break;
			}
		}

		if(mismatch==0){
			break;
		}
		device_found--;
	}

	if(mismatch==0){
		device_found_index = device_found-1;
		ret = auths_set_sdk_active_device(device_found_index);
		if(ret != SDK_SUCCESS){
			return ret;
		}
		return SDK_SUCCESS;
	}else{
		return SDK_E_UID_NOT_ENUM;
	}	
}


/**
* @brief Set SDK active I2C address used by the SDK
* @param i2c_address I2C address on the bus
*/
uint16_t auths_sdk_set_active_i2c_address(uint8_t i2c_address){

	if((i2c_address <= 0x10) || (i2c_address >= 0x80) ){
		return INF_I2C_E_ADDRESS;
	}else{
		auths_capability.interface.i2c.device_address = i2c_address;
		return SDK_SUCCESS;
	}
}

/**
* @brief Get SDK active I2C address used by the SDK
* @param i2c_address I2C address on the bus
*/
uint16_t auths_sdk_get_active_i2c_address(uint8_t *i2c_address){

	*i2c_address = auths_capability.interface.i2c.device_address;
	return SDK_SUCCESS;	
}

/**
* @brief Search for all i2c address on the bus
* @param i2c_address address on the bus
* @param UID Unique ID found on the bus
* @param device_count number of devices found on the bus
*/
uint16_t auths_exe_search_i2c_address(uint16_t* i2c_address, uint8_t* UID, uint8_t* device_count){
	//uint16_t found_address[2]={0x0000, 0x0000};
	uint8_t i2c_addr=0;
	uint16_t ret;
	uint8_t device_found = 0;
	uint8_t sfr_value;

	if((i2c_address==NULL) || (device_count==NULL)){
		return EXE_E_INPUT;
	}

//I2C search

	// Host support 7-bit address only
	// There are 112 valid address only
	for(i2c_addr=0x10; i2c_addr<0x80; i2c_addr++){
		ret = i2c_bus_command_RD(i2c_addr, AUTHS_SFR_DEVICE_ADDR, 1, &sfr_value);
		//ret = i2c_bus_command_BRES(i2c_addr);
	    if(ret == INF_I2C_SUCCESS){
	    	i2c_address[device_found]=i2c_addr;
	    	auths_capability.interface.i2c.device_address=i2c_addr;
			
			ret = Intf_ReadRegister(AUTHS_UID_ADDR, UID+(device_found*12), auths_capability.uid_length);
			if (ret != SDK_INTERFACE_SUCCESS) {
				return EXE_READUIDFAILED;
			}
			ret = i2c_bus_command_BRES(i2c_addr);
	       device_found++;
	       delay_ms(100);
	      }
	   }
	
	//the first found device address will be added to the local reference
	auths_capability.interface.i2c.device_address = (uint8_t)i2c_address[0];

	//Try using the default address if no device found from search
	if(device_found == 0){	 
		ret = i2c_bus_command_BRES(AUTHS_DEFAULT_ADDRESS);
		if(ret != INF_I2C_SUCCESS){
			return ret;
		}
		ret = i2c_bus_command_RD(AUTHS_DEFAULT_ADDRESS, AUTHS_SFR_DEVICE_ADDR,  1, &sfr_value);
		if(ret != INF_I2C_SUCCESS){
			return EXE_SEARCH_I2C_NO_DEVICE_FOUND;			
		}else{
	   		i2c_address[device_found]=AUTHS_DEFAULT_ADDRESS;
			device_found=1;
		}
		auths_capability.interface.i2c.device_address = AUTHS_DEFAULT_ADDRESS;
		ret = Intf_ReadRegister( AUTHS_UID_ADDR, UID, auths_capability.uid_length);
		if (ret != SDK_INTERFACE_SUCCESS) {
			return EXE_READUIDFAILED;
		}

	}

	*device_count = device_found;
	auths_capability.interface.i2c.device_count = device_found;
	auths_enumeration.device_found = device_found;
	
	if(device_found==0){
		return EXE_SEARCH_I2C_NO_DEVICE_FOUND;
	}

	ret = update_active_device_capabilities();
	if(ret!= SDK_SUCCESS){
		return ret;		
	}
	return EXE_SUCCESS;	
}

/**
* @brief Read I2C SFR directly
* @param i2c_address I2C address of current device
* @param sfr_address SFR address to be read
* @param sfr_value SFR value to read
* @param length Byte length to read
*/
uint16_t auths_read_i2c_sfr(uint8_t i2c_address, uint16_t sfr_address, uint8_t *sfr_value, uint16_t length)
{
	uint16_t res;
	if((sfr_value==NULL) || (length==0)){
		return INF_I2C_E_INPUT;
	}
	res = i2c_bus_command_RD(i2c_address, sfr_address, length, sfr_value);	
	if (res != INF_I2C_SUCCESS) {
		return INF_I2C_E_SFRREAD;
	} else {
		return INF_I2C_SUCCESS;
	}
}

/**
* @brief Write I2C SFR directly
* @param i2c_address I2C address of current device
* @param sfr_address SFR address to be written
* @param sfr_value SFR value to be written 
* @param length Byte length to be written 
*/
uint16_t auths_write_i2c_sfr(uint8_t i2c_address, uint16_t sfr_address, uint8_t *sfr_value, uint16_t length)
{
	uint16_t res;
	if((sfr_value==NULL) || (length==0)){
		return INF_I2C_E_INPUT;
	}
	res = i2c_bus_command_WD(i2c_address, sfr_address, length, sfr_value);	
	if (res != INF_I2C_SUCCESS) {
		return INF_I2C_E_SFRWRITE;
	} else {
		return INF_I2C_SUCCESS;
	}
}

/**
* @brief Read the value stored in I2C address register
* @param current_i2c_address I2C address of current device
* @param i2c_address_register i2c address stored in the register
*/
uint16_t auths_get_i2c_address (uint8_t current_i2c_address, uint8_t* i2c_address_register){

	uint16_t res;	

	res = auths_read_i2c_sfr(current_i2c_address, AUTHS_SFR_DEVICE_ADDR, i2c_address_register, 1);
	if(res != INF_I2C_SUCCESS){
		return INF_I2C_E_SFRREAD;
	}else{
		return SDK_SUCCESS;
	}
}

/**
* @brief Set the value stored in I2C address register
* @param current_i2c_address I2C address of current device
* @param new_i2c_address new i2c address for the device 
*/
uint16_t auths_set_i2c_address (uint8_t current_i2c_address, uint8_t new_i2c_address){

	uint16_t ret;

//#if (HOST_AUTHENTICATION_FEATURE_SUPPORTED==1)
//	if(auths_capability.host_auth_done==host_auth_not_done){
//		return SDK_E_HOST_AUTH;
//	}
//#endif

	if(auths_capability.active_interface==I2C){
		ret = i2c_bus_command_WDA (current_i2c_address, new_i2c_address);
		if(ret != INF_I2C_SUCCESS){		
			return SDK_E_WRITEADDRESS;
		}
		auths_capability.interface.i2c.device_address=current_i2c_address;
	}
	
	return SDK_SUCCESS;
}


/**
* @brief Returns the active device UID
* @param uid UID value
* @param vid VID value
* @param pid PID value
*/
uint16_t auths_exe_read_uid(uint8_t* uid, uint8_t *vid, uint8_t *pid){
	uint16_t res;

	if((uid==NULL)||(vid==NULL)||(pid==NULL)){
		return EXE_E_INPUT;
	}

	memset(auths_capability.uid, 0, auths_capability.uid_length);

	if(auths_capability.active_interface==I2C){
		res = Intf_ReadRegister(AUTHS_UID_ADDR, uid, auths_capability.uid_length);
		if (res != SDK_INTERFACE_SUCCESS) {
			return EXE_READUIDFAILED;
		}
	}



	memcpy(auths_capability.uid, uid, auths_capability.uid_length);
	memcpy(vid, uid+10, 2);
	memcpy(pid, uid+8, 2);

	//Performs simple check against NVM memory size declaration
	switch((auths_capability.uid[0] >> 1) &0x0F){
		case 0x0:
		case 0x3:
		case 0x6:
		case 0x9:
		case 0xC:{
			if(CONFIG_NVM_SIZE!=1){
				PRINT("NVM size 1Kbit\r\n");
				PRINT("Warning: Make sure that CONFIG_NVM_SIZE maccro is set to 1\r\n");
			}
			break;
		}
		case 0x1:
		case 0x4:
		case 0x7:
		case 0xA:
		case 0xD:{
			if(CONFIG_NVM_SIZE!=2){
				PRINT("NVM size 2Kbit\r\n");
				PRINT("Warning: Make sure that CONFIG_NVM_SIZE macro is set to 2\r\n");
			}
			break;
		}
		case 0x2:
		case 0x5:
		case 0x8:
		case 0xB:
		case 0xE:{
			if(CONFIG_NVM_SIZE!=5){
				PRINT("NVM size 5Kbit\r\n");
				PRINT("Warning: Make sure that CONFIG_NVM_SIZE macro is set to 5\r\n");
			}
			break;
		}
	}

	//Checks for device configuration
	switch((auths_capability.uid[0] >> 1) &0x0F){
		case 0x0:
		case 0x1:
		case 0x2:{
			if(HOST_AUTHENTICATION_FEATURE_SUPPORTED==1){
				if(auths_get_ifx_config(HOSTAUTH_CONFIG)== APP_HA_DISABLE){
					PRINT("Host Authentication is disabled on device\r\n");
					PRINT("Warning: Make sure that HOST_AUTHENTICATION_FEATURE_SUPPORTED macro is set to 0\r\n");
				}
			}
			break;
		}
		case 0x3:
		case 0x4:
		case 0x5:
		case 0x6:
		case 0x7:
		case 0x8:
		case 0x9:
		case 0xA:
		case 0xB:
		case 0xC:
		case 0xD:
		case 0xE:
		{
			if(HOST_AUTHENTICATION_FEATURE_SUPPORTED!=1){
				if(auths_get_ifx_config(HOSTAUTH_CONFIG)== APP_HA_ENABLE){
					PRINT("Host Authentication is enabled on device\r\n");
					PRINT("Warning: Make sure that HOST_AUTHENTICATION_FEATURE_SUPPORTED macro is set to 1\r\n");
				}
			}
			break;
		}
		case 0xF:{
			if(ENABLE_HOST_SUPPORT!=1){
				PRINT("Host Support mode device.\r\n");
				PRINT("Warning: Make sure that ENABLE_HOST_SUPPORT macro is set to 1\r\n");
			}
			break;
		}
	}

	return EXE_SUCCESS;
}

/**
* @brief Returns the active device interface status to external program
* @param active_interface I2C, SWI or undefined active interface status
*/
uint16_t auths_get_active_interface(INTERFACE_TYPE* active_interface) {
	
	if(active_interface == NULL||
			auths_capability.active_interface == UNDEFINED_INTERFACE){
		return SDK_E_INTERFACE_UNDEFINED;
	}

	*active_interface = auths_capability.active_interface;
	return AUTHS_SUCCESS;
}

/**
* @brief Returns the active device host authentication done status to external program
*/
uint16_t auths_is_host_auth_done(HOST_AUTH_STATUS* status){

	if(status==NULL){
		return SDK_E_INPUT;
	}
	if(auths_capability.host_auth_done==host_auth_not_done){
		*status = host_auth_not_done;		
	}
	else{
		*status = host_auth_done;
	}
	return AUTHS_SUCCESS;
}

/**
* @brief Returns the active device capability to external program
*/
uint16_t auths_get_device_capability(AuthS_Capability* device_capability){	
	memcpy(device_capability, &auths_capability, sizeof(auths_capability));
	return AUTHS_SUCCESS;
}

/**
* @brief Soft reset of the active device
*/
uint16_t auths_exe_reset(void) {	
	
	switch(auths_capability.active_interface)
	{

		case I2C:
			//the software rest delay is implemented in the command
			i2c_bus_command_BRES(auths_capability.interface.i2c.device_address);		
			break;



		case SWI:

			break;

		default:
			return EXE_E_POWERDOWN;
	}
	return EXE_SUCCESS;
}

/**
* @brief Power cycle the active device
*/
uint16_t auths_exe_power_cycle(void) {

	switch(auths_capability.active_interface)
	{

		case I2C:
			i2c_bus_command_PDWN(auths_capability.interface.i2c.device_address);
			delay_ms(10);
			i2c_bus_command_BRES(auths_capability.interface.i2c.device_address);		
			break;



		case SWI:
		
			break;

		default:			
			return EXE_E_POWERDOWN;
	}

	//power up delay is 10ms
	//power up delay is 5ms for 0C to 40C
	//software reset is 1ms
	delay_ms(10);
	return EXE_SUCCESS;
}

/**
* @brief Power down the active device
*/
uint16_t auths_exe_power_down(void) {

	switch(auths_capability.active_interface)
	{

		case I2C:
			i2c_bus_command_PDWN(auths_capability.interface.i2c.device_address);	
			break;



		case SWI:
		
			break;

		default:
			return EXE_E_POWERDOWN;
	}
	return EXE_SUCCESS;
}

/**
* @brief Power up the active device
*/
uint16_t auths_exe_power_up(void) {
	switch(auths_capability.active_interface){

		case I2C:			
			i2c_bus_command_BRES(auths_capability.interface.i2c.device_address);		
			break;



		case SWI:			

			break;

		default:			
			return EXE_E_POWERDOWN;
	}
	return EXE_SUCCESS;
}

/**
* @brief Frees the SDK resource used by the active device
*/
uint16_t auths_deinit_sdk(void) {

	uint16_t ret;

	//Power down device first
	ret = auths_exe_power_down();
	if(ret!= EXE_SUCCESS){
		return ret;
	}
	if(auths_capability.active_interface==I2C){
		auths_capability.interface.i2c.device_address=0;
		auths_capability.interface.i2c.frequency=0;
	}
	
	
	auths_capability.active_interface= UNDEFINED_INTERFACE;
	memset(auths_capability.uid, 0, auths_capability.uid_length);

	//Disable all alternate hardware references
	auths_capability.auths_alt_rng = NULL;
	auths_capability.auths_alt_sha = NULL;

	auths_capability.ecc_done=ecc_not_done;
	auths_capability.host_auth_done=host_auth_not_done;

	return SDK_SUCCESS;
}

/**
* @brief Update SDK active device capabilities
*/
static uint16_t update_active_device_capabilities(void){

	uint16_t ret;

	ret = auths_get_ifx_config(HOSTAUTH_CONFIG);
	if((ret != APP_HA_ENABLE) && (ret != APP_HA_DISABLE)){
		return ret;
	}else if (ret == APP_HA_ENABLE){
		auths_capability.device_attribute.host_authentication_support=supported;
	} else{
		auths_capability.device_attribute.host_authentication_support=unsupported;
	}
	
	if(auths_get_ifx_config(KILL_CONFIG)== APP_ECC_ENABLE_KILL){
		auths_capability.device_attribute.kill_ecc_support=supported;
	}else{
		auths_capability.device_attribute.kill_ecc_support=unsupported;
	}

	if(auths_get_ifx_config(AUTOKILL_CONFIG)== APP_ECC_ENABLE_AUTOKILL){
		auths_capability.device_attribute.auto_kill_ecc=supported;
	}else{
		auths_capability.device_attribute.auto_kill_ecc=unsupported;
	}

	if(auths_get_ifx_config(LSC_RESET_CONFIG)== APP_LSC_ENABLE_RESET){
		auths_capability.device_attribute.lsc_restore_support=supported;
	}else{
		auths_capability.device_attribute.lsc_restore_support=unsupported;
	}
	
	if(auths_get_ifx_config(NVM_RESET_CONFIG)== APP_NVM_ENABLE_RESET){
		auths_capability.device_attribute.user_nvm_unlock=supported;
	} else{
		auths_capability.device_attribute.user_nvm_unlock=unsupported;
	}

	return SDK_SUCCESS;
}
